-module(kcp).
-export([createKCP/1, send/2, recv/1, input/2, update/2]).
-include("kcppack.hrl").
-record(kcpObj, {sendId=0, windSz=10, winStart=0, winEnd=10, ackWinStart=0, ackWinEnd=20, interval=0.05, accTime=0, maxRecvNum=999, sendQueue=[], recvQueue=[], sendBuf=[], recvBuf=[], ackYet=sets:new(), recvList, isClose=false, closeEventHandler, outputFunc}).

createKCP(Wsz) ->
	DoubleWsz = Wsz * 2,
	#kcpObj{windSz=Wsz, winEnd=Wsz, ackWinEnd = DoubleWsz, recvList=looplist:createLoopList(Wsz)}.


send(KCPObj, Data) ->
	Pack = #kcppack{
		data = Data
	},

	SendQueue = KCPObj#kcpObj.sendQueue,

	KCPObj#kcpObj{sendQueue= SendQueue ++ [Pack]}.


recv(KCPObj) -> 
	RecvBuf = KCPObj#kcpObj.recvBuf,
	Count = length(RecvBuf),
	case Count > 0 of
		true ->
			[H|T] = RecvBuf,
			[H, KCPObj#kcpObj{recvBuf=T}];
		false ->
			[undefined, KCPObj]
	end.

input(KCPObj, Data) ->
	KCPPacket = #kcppack{},
	KCPPacket1 = kcppacket:decodeData(KCPPacket, Data),
	RecvQueue = KCPObj#kcpObj.recvQueue,
	KCPObj#kcpObj{recvQueue = [KCPPacket1 | RecvQueue]}.

update(KCPObj, Delta) ->
	AccTime = KCPObj#kcpObj.accTime+Delta,
	Interval = KCPObj#kcpObj.interval,
	KCPObj1 = KCPObj#kcpObj{accTime=AccTime},
	case AccTime > Interval of
		true->
			handleSend(handleAcked(handleRecv(KCPObj1)));
		false->
			KCPObj1
	end.

handleRecv(KCPObj)->
	loopRecv(KCPObj, 0).

loopRecv(KCPObj, C) ->
	case C > KCPObj#kcpObj.maxRecvNum of
		true ->
			KCPObj;
		false ->
			GoOn = readRecv(KCPObj),
			case element(1, GoOn)  of
				false ->
					KCPObj;
				true ->
					loopRecv(element(2, GoOn), C+1)
			end
	end.

readRecv(KCPObj) ->
	RecvQueue = KCPObj#kcpObj.recvQueue,
	RecPack = case length(RecvQueue) > 0 of
		true ->
			popQueue(KCPObj, RecvQueue);
		false ->
			{undefined, KCPObj}
	end,

	case element(1, RecPack) of
		undefined ->
			{false, KCPObj};
		Other ->
			{true, readPack(element(2, RecPack), element(1, RecPack))}
	end.

popQueue(KCPObj, RecvQueue)->
	[H | T] = RecvQueue,
	{H, KCPObj#kcpObj{recvQueue=T}}.




% KCPObj 返回回去
readPack(KCPObj, RecPack) ->
	WinSz = KCPObj#kcpObj.windSz,
	AckStart = KCPObj#kcpObj.ackWinStart,
	AckEnd = KCPObj#kcpObj.ackWinEnd,
	SrvSn = RecPack#kcppack.sn,
	AckYet = KCPObj#kcpObj.ackYet,
	RecvList = KCPObj#kcpObj.recvList,

	case RecPack#kcppack.isAck of
		1 ->
			SendBuf = loopSendBuf(RecPack, KCPObj#kcpObj.sendBuf),
			KCPObj#kcpObj{sendBuf=SendBuf};
		0 ->
			case checkInWin(SrvSn, KCPObj#kcpObj.ackWinStart, KCPObj#kcpObj.ackWinEnd) of
				true -> 
					SendACK = #kcppack{isAck=1, sn=RecPack#kcppack.sn},
					Func = KCPObj#kcpObj.outputFunc,
					Func(kcppacket:encodeFull(SendACK)),
					KCPObj1 = case checkInWin(SrvSn, AckStart+WinSz, AckEnd) of
						true ->
							KCPObj#kcpObj{ackWinStart=AckStart+1, ackWinEnd=AckEnd+1, ackYet=sets:del_element(AckStart, AckYet), recvList=looplist:moveStart(RecvList)};
						false ->
							KCPObj
					end,

					RecvList1 = KCPObj1#kcpObj.recvList,
					AckStart1 = KCPObj1#kcpObj.ackWinStart,

					case sets:is_element(SrvSn, AckYet) of
						false ->
							KCPObj2 = KCPObj1#kcpObj{ackYet=sets:add_element(SrvSn, AckYet), recvList=looplist:addPacket(RecvList1, RecPack, SrvSn-AckStart1)},
							checkLoopList(KCPObj2);
						true ->
							KCPObj
					end;
				false ->
					KCPObj
			end
	end.

checkLoopList(KCPObj) ->
	loopTake(KCPObj).

loopTake(KCPObj) ->
	RecvList = KCPObj#kcpObj.recvList,
	{Pack, RecvList1} = looplist:takePacket(RecvList),

	case Pack of 
		undefined ->
			KCPObj;
		Other ->
			KCPObj1 = KCPObj#kcpObj{recvList=RecvList1},
			loopTake(recvBufEnq(KCPObj1, Pack))
	end.


recvBufEnq(KCPObj, Pack) ->
	RecvBuf = KCPObj#kcpObj.recvBuf,
	KCPObj#kcpObj{recvBuf =  RecvBuf ++ [Pack]}.



% head ++  ++  tail
loopSendBuf(Seg, SendBuf) ->
	[H | T] = SendBuf,
	case H#kcppack.sn == Seg#kcppack.sn of
		true ->
			[H#kcppack{cmd=cmd_acked} | T];
		false ->
			[H | loopSendBuf(Seg, T)]
	end.

checkInWin(Id, ST, End) ->
	case ST > End of
		true ->
			(Id >= ST) or (Id < End);
		false ->
			(Id >= ST) and (Id < End)
	end.


handleAcked(KCPObj)->
	SendBuf = KCPObj#kcpObj.sendBuf,
	Count = length(SendBuf),
	case Count > 0 of
		true ->
			loopSendBuf(KCPObj, [], SendBuf);
		false ->
			KCPObj
	end.

% 整体的FistSendBuf 但是当前的SendBuf
% 复杂的kcpObj 应该用ets来存储数据 特不是record
% 每个module 一个 process
% process的粒度 process 
loopSendBuf(KCPObj, Head, SendBuf) ->
	Count = length(SendBuf),
	case Count == 0 of
		true ->
			KCPObj;
		false ->
			[H | T] = SendBuf,
			WinStart = KCPObj#kcpObj.winStart,
			WinEnd = KCPObj#kcpObj.winEnd,
			{KCPObj2, SendBuf2, Remove} = case H#kcppack.cmd of
				cmd_acked ->
					KCPObj1 = case length(Head) == 0 of
						true ->
							KCPObj#kcpObj{winStart=WinStart+1, winEnd=WinEnd+1};
						false ->
							KCPObj
					end,
					{KCPObj1, T, true};
				Other ->
					{KCPObj, SendBuf, false}
			end,

			case Remove of
				true ->
					loopSendBuf(KCPObj2, Head, SendBuf2);
				false ->
					loopSendBuf(KCPObj2, Head ++ [H], T)
			end
	end.


handleSend(KCPObj) ->
	A=1.




